package com.qfedu.edu.classservice.service.impl;

import com.alibaba.fastjson.JSON;
import com.qfedu.edu.classservice.feign.StockStorageService;
import com.qfedu.edu.classservice.utils.AliOssCloudUtils;
import com.qfedu.edu.classservice.utils.FTPUtil;
import com.qfedu.edu.constance.FTPConstance;
import com.qfedu.edu.constance.RedisConstance;
import com.qfedu.edu.exception.BusinessException;
import com.qfedu.edu.manager.RedisManager;
import com.qfedu.edu.po.Class;
import com.qfedu.edu.classservice.mapper.ClassMapper;
import com.qfedu.edu.classservice.service.ClassService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qfedu.edu.result.R;
import com.qfedu.edu.result.ResponseCode;
import com.qfedu.edu.utils.FileNameUtils;
import com.qfedu.edu.vo.AddClassVo;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author xiaobobo
 * @since 2024-11-06
 */
@Service
@Slf4j
public class ClassServiceImpl extends ServiceImpl<ClassMapper, Class> implements ClassService {


    @Autowired
    private ClassMapper classMapper;

    @Autowired
    private StockStorageService stockStorageService;

    @Autowired
    private RedisManager redisManager;


    @Autowired
    private ResourceLoader resourceLoader;

    /**
     * 这个方法就需要分布式的事务来加持
     * @param addClassVo
     * @param file
     * @throws IOException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @GlobalTransactional
    @Override
    public void addClass(AddClassVo addClassVo, MultipartFile file) throws IOException, InvocationTargetException, IllegalAccessException, TemplateException {
        //这里是编写添加课程的逻辑
        //校验参数
        checkParams(addClassVo,file);
        //将图片上传到阿里云的OSS上
        String path=uploadPhotoToOSS(file);
        //给上述的对象设置这个键值对
        addClassVo.setPhoto(path);
        //向数据库中插入数据
        addClassToDB(addClassVo);
        //添加库存信息
        int realCount=addStock(addClassVo);
        //将库存信息添加到Redis中去
        addStockToRedis(addClassVo,realCount);
        //生成详情页(Freemarker、FTP)
        createStaticHTML(addClassVo,realCount);
    }

    /**
     * 生成详情页面的这个代码
     * @param addClassVo：添加的VO对象
     * @param realCount：真实库存
     */
    private void createStaticHTML(AddClassVo addClassVo,int realCount) throws TemplateException, IOException {
        //先将页面要显示的的数据放到Map中去
        //将我们传递过来的数据 放到一个hashmap中
        Map<String,Object> objectMap=new HashMap<>();
        //接下来向里面放数据
        objectMap.put("className",addClassVo.getClassName());
        objectMap.put("photo",addClassVo.getPhoto());
        objectMap.put("classDes",addClassVo.getClassDesc());
        objectMap.put("classId",addClassVo.getClassId());
        objectMap.put("price",addClassVo.getPrice());
        objectMap.put("seckillPrice",addClassVo.getSecKillPrice());
        objectMap.put("quanty",realCount);

        //TODO 这里是修改路径
//        String path=resourceLoader.getResource("classpath:templates/").getURI().getPath();

        String path="/usr/local/pro/templates/";

        log.info("模板路径是:"+path);


        //获取模板所在的根路径
//        String path=ClassServiceImpl.class.getResource("/").getPath()+"templates/";
        //判断这个课程的id是否为空 你们觉得为什么要判断这个id是否为空呢?
        // 是因为 我们最终生成的这个HTML文件的名字 就是 课程id.html
        if(com.alibaba.druid.util.StringUtils.isEmpty(addClassVo.getClassId())){
            throw new BusinessException(ResponseCode.CLASS_ID_NOT_FOUND_EXCEPTION);
        }
        //执行到这里说明参数无误
        //获取整个文件的生成路径
        //程序执行到这里 说明有id
        String htmlPath=generateHTML(path,"class_detail.ftl",objectMap,addClassVo.getClassId()+".html",path);
        //最终上传这个文件到 Linux服务器上
        uploadHTMLToLinuxServer(htmlPath);
    }

    /**
     * 这里就是生成HTML代码的地方
     * @param templatePath：模板所在的路径
     * @param templateName：模板的名字
     * @param objectMap：模板中需要的数据
     * @param desFileName：生成HTML文件的这个文件名
     * @param htmlPath：最终的HTML文件所在的路径
     * @return
     * @throws IOException
     * @throws TemplateException
     */
    private String generateHTML(String templatePath, String templateName, Map<String, Object> objectMap, String desFileName, String htmlPath) throws IOException, TemplateException {
        //第一步：通过模板的路径和模板的名字 找到这个模板
        Template template= getTemplateConfiguration(templatePath).getTemplate(templateName);
        //设置编码
        template.setEncoding("UTF-8");
        //创建目录
        createDir(htmlPath);
        //找到最终生成了HTML文件所在的这个位置
        File htmlFile = new File(htmlPath + desFileName);
        //第五步：给这个文件添加输出流
        FileOutputStream fileOutputStream = new FileOutputStream(htmlFile);
        //第六步：最终的目的是要将模板中的内容输出到 上面的文件中
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream,"UTF-8"));
        //输出内容到文件
        template.process(objectMap,bufferedWriter);
        bufferedWriter.close();
        fileOutputStream.close();
        log.info("HTML页面生成成功....");
        return htmlPath + desFileName;
    }

    /**
     * 这个路径不存在的话 还要创建
     * @param htmlPath
     */
    private void createDir(String htmlPath) throws IOException {
        File file = new File(htmlPath);
        if(!file.exists()){
            file.createNewFile();
        }
    }

    /**
     * 通过模板的路径和名字找到这个模板
     * @param templatePath
     * @return
     */
    private Configuration getTemplateConfiguration(String templatePath) {
        Configuration configuration = new Configuration();
        //设置了配置的编码
        configuration.setEncoding(Locale.CHINA,"UTF-8");
        configuration.setClassForTemplateLoading(this.getClass(),
                "/websiteroot/freemarker");
        try {
            configuration.setDirectoryForTemplateLoading(new File(
                    templatePath));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return configuration;
    }

    /**
     * 这个方法的主要作用就是将生成的这个HTML文件上传到 Linux服务器上
     * @param htmlPath
     */
    private void uploadHTMLToLinuxServer(String htmlPath) {
        //这里文件的上传要通过FTP这个协议去实现上传
        //要上传文件
        FTPClient ftpClient = new FTPUtil().getFTPClient(
                FTPConstance.getFtpHost(),
                FTPConstance.getFtpPort(),
                FTPConstance.getFtpUsername(),
                FTPConstance.getFtpPassword());
        //接下来就可以调用咋们的API完成文件的上传了
        FTPUtil.uploadFile(ftpClient,htmlPath,FTPConstance.getUploadPath());
        log.info("静态页面上传到Openresty成功....");

    }
    /**
     * 添加库存信息到Redis中去
     * @param addClassVo
     */
    private void addStockToRedis(AddClassVo addClassVo,int realCount) {
        //设置问题：
        //使用什么数据类型呢? String类型  key：pro:stock:课程id   数量
        String key=RedisConstance.getProStockPrefix()+addClassVo.getClassId();
        //这里还需要计算过期时间
        //首先获取到的是秒杀的结束时间
        Date endTime = addClassVo.getEndTime();
        //在利用当前时间和结束时间时间做减法

        Long expire= (endTime.getTime()-DateTime.now().getMillis())/1000;

        //接下来将这个数据放到数据库中去
        redisManager.addObject(key,realCount,30, TimeUnit.DAYS);
    }

    /**
     * 添加库存信息
     * @param addClassVo
     */
    private int addStock(AddClassVo addClassVo) {
        R r=stockStorageService.addStock(addClassVo.getClassId(), addClassVo.getCount());
        log.info("添加库存信息成功....");
        return (int) r.getDataMap().get("realCount");
    }

    /**
     * 向数据库中插入数据
     * @param addClassVo
     */
    private void addClassToDB(AddClassVo addClassVo) throws InvocationTargetException, IllegalAccessException {
        //将VO转换成PO对象
        Class aClass = new Class();
        //那就改成最普通的玩法
        aClass.setClassDesc(addClassVo.getClassDesc());
        aClass.setClassName(addClassVo.getClassName());
        aClass.setModel(addClassVo.getModel());
        aClass.setPrice(addClassVo.getPrice());
        aClass.setSecKillPrice(addClassVo.getSecKillPrice());
        aClass.setStartTime(addClassVo.getStartTime());
        aClass.setEndTime(addClassVo.getEndTime());
        aClass.setPhoto(addClassVo.getPhoto());

        //然后就可以向数据库中插入数据了
        classMapper.insert(aClass);
        log.info("保存数据到课程表成功....");
        addClassVo.setClassId(aClass.getClassId());
        //同时将商品信息添加到Redis中
        addProToRedis(aClass);
    }

    /**
     * 将商品信息添加到Redis中去
     * @param aClass
     */
    private void addProToRedis(Class aClass) {
        //设计:  pro:info:课程id  商品信息
        String key=RedisConstance.getProInfoPrefix()+aClass.getClassId();
        //值：设置成JSON格式就可以了
        String val = JSON.toJSONString(aClass);
        //然后将信息存储到Redis中去
        //过期时间的计算
        Date endTime = aClass.getEndTime();
        //在利用当前时间和结束时间时间做减法
        //Long expire= (endTime.getTime()-DateTime.now().getMillis())/1000;
        redisManager.addString(key,val,30,TimeUnit.DAYS);
    }

    /**
     * 上传图片到阿里云的OSS上
     * @param file
     * @return
     */
    private String uploadPhotoToOSS(MultipartFile file) throws IOException {
        InputStream inputStream = file.getInputStream();
        if(null==inputStream){
            return "";
        }
        //这里还要使用一个工具类来生成文件的名字
        String fileName = FileNameUtils.getFileName(".png");
        String path = new AliOssCloudUtils().uploadFile2OSS(file.getInputStream(), fileName);
        log.info("上传图片到阿里云的OSS成功...path:{}",path);
        return path;
    }

    /**
     * 校验参数的合法性
     * @param addClassVo
     */
    private void checkParams(AddClassVo addClassVo,MultipartFile file) {
        if(null==addClassVo|| StringUtils.isEmpty(addClassVo.getClassName())||null==addClassVo.getPrice()||null==addClassVo.getSecKillPrice()||null==addClassVo.getCount()||null==addClassVo.getStartTime()||null==addClassVo.getEndTime()||null==addClassVo.getModel()||null==addClassVo.getHorse()||null==file){
          throw new BusinessException(ResponseCode.PARAM_EXCEPTION);
        }
        log.info("参数校验完成....");
    }
}
